﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.ServiceModel.DomainServices.Client;
using System.Collections.ObjectModel;

namespace TelecoSystems.Infrastructure.Helpers
{
    /// <summary>
    /// A wrapper class that exposes an <see cref="EntityList{T}"/> as an implementation
    /// of <see cref="IEnumerable{T}"/>, <see cref="ICollectionView{T}"/>, <see cref="IEditableCollectionView"/>,
    /// <see cref="INotifyCollectionChanged"/>, and <see cref="INotifyPropertyChanged"/>.
    /// </summary>
    /// <typeparam name="T">
    /// The type of entity included in the collection.
    /// <para>
    /// If <typeparamref name="T"/> is unknown at design time, use the <see cref="EntityCollectionView.From"/> factory method to create
    /// an appropriate instance of <see cref="EntityCollectionView{T}"/>.
    /// </para>
    /// </typeparam>
    public class EntityCollectionView<T> : IEnumerable<T>, 
        ICollectionView, IEditableCollectionView, 
        INotifyCollectionChanged, INotifyPropertyChanged
        where T : Entity, IEditableObject, new()
    {
        #region PropertyChangedEventArgs

        private static PropertyChangedEventArgs CurrentItemChangedEventArgs = new PropertyChangedEventArgs("CurrentItem");
        private static PropertyChangedEventArgs CurrentPositionChangedEventArgs = new PropertyChangedEventArgs("CurrentPosition");
        private static PropertyChangedEventArgs IsCurrentBeforeFirstChangedEventArgs = new PropertyChangedEventArgs("IsCurrentBeforeFirst");

        #endregion

        #region Private Fields and Properties

        private EntitySet<T> _entitySet;
        private T _currentAddItem;
        private T _currentEditItem;
        private SortDescriptionCollection _sortDescriptions;

        private IEnumerable<T> Enumerable
        {
            get { return this._entitySet; }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Subscribe to the <see cref="INotifyCollectionChanged"/> events on the source collection,
        /// relaying the events from ourself.
        /// </summary>
        /// <remarks>
        /// If we don't have a current item, we will attempt to set currency to the first item.
        /// </remarks>
        /// <param name="collection">The source collection to relay events from.</param>
        private void RelayCollectionChanged(INotifyCollectionChanged collection)
        {
            collection.CollectionChanged += (s, e) =>
            {
                this.CollectionChanged(this, e);

                if (this.IsCurrentBeforeFirst && e.Action == NotifyCollectionChangedAction.Add || e.Action == NotifyCollectionChangedAction.Reset)
                {
                    this.MoveCurrentToFirst();
                    this.RaisePropertyChanged(IsCurrentBeforeFirstChangedEventArgs);
                }
            };
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Wrap around an <see cref="EntityList{T}"/>.
        /// </summary>
        /// <param name="entityList">The list to wrap.</param>
        public EntityCollectionView(EntitySet<T> entitySet)
        {
            this._entitySet = entitySet;
            this.RelayCollectionChanged(this._entitySet);
        }
        #endregion

        #region Public Properties (not from any interfaces)

        /// <summary>
        /// Get the count from the source collection.
        /// </summary>
        public int Count
        {
            get { return this._entitySet.Count; }
        }

        #endregion

        #region Public Events (not from any interfaces)

        public event EventHandler Refreshed = delegate { };

        #endregion

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator()
        {
            return this._entitySet.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        /// <summary>
        /// Explicitly implemented because we implicitly implement
        /// <see cref="IEnumerable<T>.GetEnumerator"/>.
        /// </summary>
        /// <returns>The <see cref="IEnumerator"/> from the source collection.</returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion

        #region ICollectionView Members

        public bool CanFilter
        {
            get { return false; }
        }

        public bool CanGroup
        {
            get { return false; }
        }

        public bool CanSort
        {
            get { return true; }
        }

        public bool Contains(object item)
        {
            return this.Enumerable.Contains((T)item);
        }

        public System.Globalization.CultureInfo Culture
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }

        public event EventHandler CurrentChanged = delegate { };
        public event CurrentChangingEventHandler CurrentChanging = delegate { };

        public object CurrentItem
        {
            get;
            private set;
        }

        public int CurrentPosition
        {
            get;
            private set;
        }

        public IDisposable DeferRefresh()
        {
            return new DeferRefreshHelper(() => Refresh());
        }

        public Predicate<object> Filter
        {
            get
            {
                throw new NotSupportedException("EntityCollectionView doesn't support Filter");
            }
            set
            {
                throw new NotSupportedException("EntityCollectionView doesn't support Filter");
            }
        }

        public ObservableCollection<GroupDescription> GroupDescriptions
        {
            get { return null; }
        }

        public ReadOnlyObservableCollection<object> Groups
        {
            get { return null; }
        }

        public bool IsCurrentAfterLast
        {
            get { return false; }
        }

        public bool IsCurrentBeforeFirst
        {
            get { return this.CurrentItem == null; }
        }

        public bool IsEmpty
        {
            get { return this._entitySet.Count == 0; }
        }

        public bool MoveCurrentTo(object item)
        {
            if (this.IsEmpty || object.Equals(CurrentItem, item))
            {
                return false;
            }

            for (int i = 0; i < this.Count; ++i)
            {
                if (this.Enumerable.ElementAt(i).Equals(item))
                {
                    CurrentChangingEventArgs args = new CurrentChangingEventArgs();
                    this.CurrentChanging(this, args);

                    if (!args.Cancel)
                    {
                        this.CurrentItem = item;
                        this.CurrentPosition = i;

                        this.RaisePropertyChanged(CurrentItemChangedEventArgs);
                        this.RaisePropertyChanged(CurrentPositionChangedEventArgs);
                        this.CurrentChanged(this, EventArgs.Empty);

                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
            }

            return false;
        }

        public bool MoveCurrentToFirst()
        {
            return this.MoveCurrentToPosition(0);
        }

        public bool MoveCurrentToLast()
        {
            return this.MoveCurrentToPosition(this.Count - 1);
        }

        public bool MoveCurrentToNext()
        {
            if (this.CurrentPosition < this.Count - 1)
            {
                return this.MoveCurrentToPosition(this.CurrentPosition + 1);
            }

            return false;
        }

        public bool MoveCurrentToPosition(int position)
        {
            if (this.IsEmpty)
            {
                return false;
            }

            CurrentChangingEventArgs e = new CurrentChangingEventArgs(true);
            this.CurrentChanging(this, e);

            if (e.Cancel)
            {
                return false;
            }

            if (position == -1)
            {
                this.CurrentItem = null;
            }
            else
            {
                this.CurrentItem = this.Enumerable.ElementAt(position);
            }

            this.CurrentPosition = position;

            this.RaisePropertyChanged(CurrentItemChangedEventArgs);
            this.RaisePropertyChanged(CurrentPositionChangedEventArgs);
            this.CurrentChanged(this, EventArgs.Empty);

            return true;
        }

        public bool MoveCurrentToPrevious()
        {
            if (this.CurrentPosition > 0)
            {
                return this.MoveCurrentToPosition(this.CurrentPosition - 1);
            }

            return false;
        }

        public void Refresh()
        {
            this.Refreshed(this, EventArgs.Empty);
        }

        public SortDescriptionCollection SortDescriptions
        {
            get
            {
                if (this._sortDescriptions == null)
                {
                    this._sortDescriptions = new SortDescriptionCollection();
                }

                return this._sortDescriptions;
            }
        }

        public IEnumerable SourceCollection
        {
            get { return this._entitySet; }
        }

        #endregion

        #region IEditableCollectionView Members

        public object AddNew()
        {
            this.CommitNew();
            this.CommitEdit();

            this._currentAddItem = new T();
            this._entitySet.Add(this._currentAddItem);

            this._currentAddItem.BeginEdit();
            this.MoveCurrentToLast();

            return this._currentAddItem;
        }

        public bool CanAddNew
        {
            get { return this._entitySet.CanAdd; }
        }

        public bool CanCancelEdit
        {
            get { return true; }
        }

        public bool CanRemove
        {
            get { return this._entitySet.CanRemove; }
        }

        public void CancelEdit()
        {
            if (this.IsEditingItem)
            {
                this._currentEditItem.CancelEdit();
            }
        }

        public void CancelNew()
        {
            if (this.IsAddingNew)
            {
                this._currentAddItem.CancelEdit();
                this.MoveCurrentToLast();

                this.Remove(this._currentAddItem);
                this._currentAddItem = null;
            }
        }

        public void CommitEdit()
        {
            if (this.IsEditingItem)
            {
                this._currentEditItem.EndEdit();
                this._currentEditItem = null;
            }
        }

        public void CommitNew()
        {
            if (this.IsAddingNew)
            {
                this._currentAddItem.EndEdit();
                this._currentAddItem = null;
            }
        }

        public object CurrentAddItem
        {
            get { return this._currentAddItem; }
        }

        public object CurrentEditItem
        {
            get { return this._currentEditItem; }
        }

        public void EditItem(object item)
        {
            this.CommitNew();
            this.CommitEdit();

            this._currentEditItem = item as T;
            this.MoveCurrentTo(item);

            if (this._currentEditItem != null)
            {
                this._currentEditItem.BeginEdit();
            }
        }

        public bool IsAddingNew
        {
            get { return this._currentAddItem != null; }
        }

        public bool IsEditingItem
        {
            get { return this._currentEditItem != null; }
        }

        public NewItemPlaceholderPosition NewItemPlaceholderPosition
        {
            get
            {
                return NewItemPlaceholderPosition.None;
            }
            set
            {
                throw new NotSupportedException();
            }
        }

        public void Remove(object item)
        {
            T entity = item as T;

            if (entity != null)
            {
                this._entitySet.Remove(entity);

                if (this.IsEmpty)
                {
                    this.CurrentItem = null;
                    this.CurrentPosition = -1;

                    this.CurrentChanging(this, new CurrentChangingEventArgs(false));
                    this.RaisePropertyChanged(CurrentItemChangedEventArgs);
                    this.RaisePropertyChanged(CurrentPositionChangedEventArgs);
                    this.RaisePropertyChanged(IsCurrentBeforeFirstChangedEventArgs);
                    this.CurrentChanged(this, EventArgs.Empty);
                }
                else
                {
                    this.MoveCurrentTo(this.CurrentPosition);
                }
            }
        }

        public void RemoveAt(int index)
        {
            if (index < 0 || index >= this._entitySet.Count)
            {
                throw new IndexOutOfRangeException("index must be at least 0 and less than the Count");
            }

            this.Remove(this._entitySet.ElementAt(index));
        }

        #endregion

        #region INotifyCollectionChanged Members

        public event NotifyCollectionChangedEventHandler CollectionChanged = delegate { };

        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        protected void RaisePropertyChanged(PropertyChangedEventArgs args)
        {
            this.PropertyChanged(this, args);
        }

        #endregion

        private class DeferRefreshHelper : IDisposable
        {
            private Action _callback;

            public DeferRefreshHelper(Action callback)
            {
                _callback = callback;
            }

            public void Dispose()
            {
                _callback();
            }
        }
    }
}
